package com.zywl.utils.utils;

import de.idyl.winzipaes.AesZipFileDecrypter;
import de.idyl.winzipaes.AesZipFileEncrypter;
import de.idyl.winzipaes.impl.*;
import org.apache.commons.io.FileUtils;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipFile;
import org.apache.tools.zip.ZipOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

/**
 * zip密码解压缩
 *
 * @author yangfan
 *
 */
public final class ZipUtil {
	private static final Logger logger = LoggerFactory.getLogger(ZipUtil.class);

	private static final String ENCODING = "GBK";// 无密码压缩和解压的编码

	/**
	 * 使用指定密码将给定文件或文件夹压缩成指定的输出ZIP文件
	 *
	 * @param srcFile
	 *            需要压缩的文件或文件夹
	 * @param destPath
	 *            输出路径
	 * @param passwd
	 *            压缩文件使用的密码
	 */
	public static void zip(String srcFile, String destPath, String passwd) {
		AESEncrypter encrypter = new AESEncrypterBC();
		AesZipFileEncrypter zipFileEncrypter = null;
		try {
			zipFileEncrypter = new AesZipFileEncrypter(destPath, encrypter);
			File sFile = new File(srcFile);
			/**
			 * AesZipFileEncrypter提供了重载的添加Entry的方法,其中: add(File f, String
			 * passwd) 方法是将文件直接添加进压缩文件
			 *
			 * add(File f, String pathForEntry, String passwd)
			 * 方法是按指定路径将文件添加进压缩文件 pathForEntry - to be used for addition of the
			 * file (path within zip file)
			 */
			doZip(sFile, zipFileEncrypter, Constants.STRING_NULL, passwd);
			logger.info(destPath + "，压缩完成");
		} catch (IOException e) {
			logger.error("使用指定密码将给定文件或文件夹压缩成指定的输出ZIP文件异常", e);
		} finally {
			try {
				zipFileEncrypter.close();
			} catch (IOException e) {
				logger.error("zipFileEncrypter关闭异常", e);
			}
		}
	}

	/**
	 * 具体压缩方法,将给定文件添加进压缩文件中,并处理压缩文件中的路径
	 *
	 * @param file
	 *            给定磁盘文件(是文件直接添加,是目录递归调用添加)
	 * @param encrypter
	 *            AesZipFileEncrypter实例,用于输出加密ZIP文件
	 * @param pathForEntry
	 *            ZIP文件中的路径
	 * @param passwd
	 *            压缩密码
	 * @throws IOException
	 */
	private static void doZip(File file, AesZipFileEncrypter encrypter, String pathForEntry, String passwd)
			throws IOException {
		if (file.isFile()) {
			pathForEntry += file.getName();
			encrypter.add(file, pathForEntry, passwd);
			return;
		}
		pathForEntry += file.getName() + File.separator;
		for (File subFile : file.listFiles()) {
			doZip(subFile, encrypter, pathForEntry, passwd);
		}
	}

	/**
	 * 使用给定密码解压指定压缩文件到指定目录
	 *
	 * @param inFile
	 *            指定Zip文件
	 * @param outDir
	 *            解压目录
	 * @param passwd
	 *            解压密码
	 */
	public static void unZip(String inFile, String outDir, String passwd) {
		File outDirectory = new File(outDir);
		if (!outDirectory.exists()) {
			outDirectory.mkdir();
		}
		AESDecrypter decrypter = new AESDecrypterBC();
		AesZipFileDecrypter zipDecrypter = null;
		try {
			zipDecrypter = new AesZipFileDecrypter(new File(inFile), decrypter);
			AesZipFileDecrypter.charset = "utf-8";
			/**
			 * 得到ZIP文件中所有Entry,但此处好像与JDK里不同,目录不视为Entry
			 * 需要创建文件夹,entry.isDirectory()方法同样不适用,不知道是不是自己使用错误 处理文件夹问题处理可能不太好
			 */
			List<ExtZipEntry> entryList = zipDecrypter.getEntryList();
			for (ExtZipEntry entry : entryList) {
				String eName = entry.getName();
				String dir = eName.substring(0, eName.lastIndexOf(File.separator) + 1);
				File extractDir = new File(outDir, dir);
				if (!extractDir.exists()) {
					FileUtils.forceMkdir(extractDir);
				}
				/**
				 * 抽出文件
				 */
				File extractFile = new File(outDir + File.separator + eName);
				zipDecrypter.extractEntry(entry, extractFile, passwd);
			}

			logger.info(outDir + "，解压缩完成");
		} catch (Exception e) {
			logger.error("使用给定密码解压指定压缩文件到指定目录", e);
		} finally {
			try {
				zipDecrypter.close();
			} catch (IOException e) {
				logger.error("zipDecrypter关闭异常", e);
			}
		}
	}

	/**
	 * 无密码压缩
	 *
	 * @param source
	 *            需压缩的文件或文件夹
	 * @param target
	 *            生成的目标路径
	 * @throws Exception
	 */
	public static void zip(String source, String target) throws Exception {
		ZipOutputStream zos = null;
		BufferedInputStream bis = null;
		try {
			File file = new File(source);
			zos = new ZipOutputStream(new FileOutputStream(target));
			zos.setEncoding(ENCODING);

			if (file.isDirectory()) {
                File[] files = file.listFiles();

                for (int i = 0; i < files.length; i++) {
                    bis = new BufferedInputStream(new FileInputStream(files[i]));
                    zos.putNextEntry(new ZipEntry(file.getName() + File.separator + files[i].getName()));
                    while (true) {
                        byte[] b = new byte[100];
                        int len = bis.read(b);
                        if (len == -1)
                            break;
                        zos.write(b, 0, len);
                    }
                    bis.close();
                }
            } else {
                bis = new BufferedInputStream(new FileInputStream(file));
                zos.putNextEntry(new ZipEntry(file.getName()));
                while (true) {
                    byte[] b = new byte[100];
                    int len = bis.read(b);
                    if (len == -1)
                        break;
                    zos.write(b, 0, len);
                }
                bis.close();
            }
		} catch (Exception e) {
			throw e;
		} finally {
			if (zos != null) {
				zos.close();
			}
		}
	}

	/**
	 * 无密码解压，仅限zip
	 *
	 * @param zipFilePath
	 *            需解压的压缩包路径
	 * @param fileSavePath
	 *            解压后的文件路径
	 * @param isDelete
	 *            是否删除压缩包
	 * @throws Exception
	 */
	public static List<File> unZip(String zipFilePath, String fileSavePath, boolean isDelete) throws Exception {
		List<File> files = new ArrayList<File>();
		try {
			File f = new File(zipFilePath);
			if ((!f.exists()) && (f.length() <= 0)) {
				logger.error("要解压的文件不存在-" + zipFilePath);
			}
			ZipFile zipFile = new ZipFile(f, ENCODING);
			String strPath, gbkPath, strtemp;
			strPath = fileSavePath;
			Enumeration<ZipEntry> e = zipFile.getEntries();
			while (e.hasMoreElements()) {
				ZipEntry zipEnt = e.nextElement();
				gbkPath = zipEnt.getName().replace(Constants.STRING_SPRIT, File.separator);
				strtemp = strPath + File.separator + gbkPath;
				if (zipEnt.isDirectory()) {
					File dir = new File(strtemp);
					if (!dir.exists()) {
						dir.mkdirs();
					}
					continue;
				} else {
					// 读写文件
					InputStream is = zipFile.getInputStream(zipEnt);
					BufferedInputStream bis = new BufferedInputStream(is);
					// 建目录
					String strsubdir = gbkPath;
					for (int i = 0; i < strsubdir.length(); i++) {
						if (strsubdir.substring(i, i + 1).equalsIgnoreCase(File.separator)) {
							String temp = strPath + File.separator + strsubdir.substring(0, i);
							File subdir = new File(temp);
							if (!subdir.exists())
								subdir.mkdir();
						}
					}
					FileOutputStream fos = new FileOutputStream(strtemp);
					BufferedOutputStream bos = new BufferedOutputStream(fos);
					int len;
					byte[] buff = new byte[1024];
					while ((len = bis.read(buff)) != -1) {
						bos.write(buff, 0, len);
					}
					files.add(new File(strtemp));
					bos.close();
					fos.close();
				}
			}
			zipFile.close();
		} catch (Exception e) {
			logger.error("解压文件异常", e);
			throw e;
		} finally {
			/*
			 * 文件不能删除的原因： 1.看看是否被别的进程引用，手工删除试试(删除不了就是被别的进程占用) 2.file是文件夹
			 * 并且不为空，有别的文件夹或文件， 3.极有可能有可能自己前面没有关闭此文件的流(我遇到的情况)
			 */
			if (isDelete) {
				FileUtil.deleteFile(zipFilePath);
			}
		}
		return files;
	}
}